This JavaScript program demonstrates how to draw a plane cut by a cube with transparency in a WebGL program.
<!DOCTYPE html>
<html>
<head>
<title>XoaX.net's WebGL</title>
<script id="idVertexShader" type="c">
attribute vec4 av4Vertex;
attribute vec4 av4Color;
varying vec4 vv4Color;
void main() {
gl_Position = av4Vertex;
gl_PointSize = 5.0;
vv4Color = av4Color;
}
</script>
<script id="idFragmantShader" type="c">
precision mediump float;
varying vec4 vv4Color;
void main() {
gl_FragColor = vv4Color;
}
</script>
<script type="text/javascript">
var gqGL = null;
var gqProgram = null;
function CreateProgramAndContext() {
// Get the WebGL Context
var qCanvas = document.querySelector("#idCanvas");
gqGL = qCanvas.getContext("webgl");
// Compile the vertex shader
var sVertexShaderCode = document.querySelector("#idVertexShader").text;
var qVertexShader = gqGL.createShader(gqGL.VERTEX_SHADER);
gqGL.shaderSource(qVertexShader, sVertexShaderCode);
gqGL.compileShader(qVertexShader);
// Compile the fragment shader
var sFragmentShaderCode = document.querySelector("#idFragmantShader").text;
var qFragmentShader = gqGL.createShader(gqGL.FRAGMENT_SHADER);
gqGL.shaderSource(qFragmentShader, sFragmentShaderCode);
gqGL.compileShader(qFragmentShader);
// Compile and link the program
gqProgram = gqGL.createProgram();
gqGL.attachShader(gqProgram, qVertexShader);
gqGL.attachShader(gqProgram, qFragmentShader);
gqGL.linkProgram(gqProgram);
gqGL.useProgram(gqProgram);
}
var gfaTransformedVertices = null;
var gfaVertexColors = null;
function CreateBuffers() {
var qVerticesBuffer = gqGL.createBuffer();
gqGL.bindBuffer(gqGL.ARRAY_BUFFER, qVerticesBuffer);
gqGL.bufferData(gqGL.ARRAY_BUFFER, gfaTransformedVertices, gqGL.STATIC_DRAW);
var qVertexLoc = gqGL.getAttribLocation(gqProgram, 'av4Vertex');
gqGL.vertexAttribPointer(qVertexLoc, 4, gqGL.FLOAT, false, 0, 0);
gqGL.enableVertexAttribArray(qVertexLoc);
var qColorsBuffer = gqGL.createBuffer();
gqGL.bindBuffer(gqGL.ARRAY_BUFFER, qColorsBuffer);
gqGL.bufferData(gqGL.ARRAY_BUFFER, gfaVertexColors, gqGL.STATIC_DRAW);
var qColors = gqGL.getAttribLocation(gqProgram, 'av4Color');
gqGL.vertexAttribPointer(qColors, 4, gqGL.FLOAT, false, 0, 0);
gqGL.enableVertexAttribArray(qColors);
}
var gfaVertices = null;
var giAddedPoints = 0;
function Initialization() {
gfaVertices = new Float32Array([
// These must be drawn back to front to render it correctly with the alpha blending
-1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, // x = -1
1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, // y = -1
1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, // z = -1
1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, // x = 1
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, // y = 1
1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, // z = 1
// Put the vertices for the diamond first, then the cube vertices
0.0, 0.85, 0.0, 1.0, 0.85, 0.0, 0.85, 1.0, -0.85, 0.0, -0.85, 1.0, 0.0, -0.85, 0.0, 1.0,
// Add zeroes that will be written over
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
]);
faaEdges = [
[-1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0],
[-1, 0, -1], [1, 0, -1], [-1, 0, 1], [1, 0, 1],
[0, -1, -1], [0, 1, -1], [0, -1, 1], [0, 1, 1]
];
// Ax + By + CZ + D = 0
var faPlane = [2.0, 5.0, 3.0, 2.0];
// Get the planar-intersection polygon
var faPolygon = GetPlaneBoxIntersectionPolygon(faPlane, [[-1.0, 1.0],[-1.0, 1.0],[-1.0, 1.0]]);
// Copy the polygon vertices.
for (var i = 0; i < faPolygon.length; ++i) {
gfaVertices[28*4 + i] = faPolygon[i];
}
// Each point has 4 coordinates
giAddedPoints = faPolygon.length/4;
var iVertices = (28 + giAddedPoints);
gfaTransformedVertices = new Float32Array(4*iVertices);
gfaVertexColors = new Float32Array(4*iVertices);
CreateProgramAndContext();
// Begin the animation loop.
const kiIntervalId = setInterval(Render, 20);
}
function Render() {
// First copy the vertices before the transformation
for (var i = 4*24; i < 4*24 + 16+4*giAddedPoints; ++i) {
gfaTransformedVertices[i] = gfaVertices[i];
}
// Color the first four vertices
for (var i = 0; i < 4; ++i) {
// Set the colors too
gfaVertexColors[4*24 + 4*i] = 1.0;
gfaVertexColors[4*24 + 4*i + 1] = 1.0;
gfaVertexColors[4*24 + 4*i + 2] = 1.0;
gfaVertexColors[4*24 + 4*i + 3] = 1.0;
}
for (var i = 0; i < giAddedPoints; ++i) {
gfaVertexColors[4*28 + 4*i] = 0.0;
gfaVertexColors[4*28 + 4*i + 1] = 0.0;
gfaVertexColors[4*28 + 4*i + 2] = 1.0;
gfaVertexColors[4*28 + 4*i + 3] = .75;
}
var faLookAtMatrix = CreateLookAtMatrix([1, 3, 2],[0, 0, 0],[0, 1, 0]);
// Create the orthographic matrix
var faOrthoMatrix = CreateOrthographicMatrix(-2.0, 2.0, -2.0, 2.0, 2.0, -2.0);
MultiplyMatrices(faOrthoMatrix, faLookAtMatrix);
// Add the extra colors for the view cube
for (var iFace = 0; iFace < 6; ++iFace) {
var fBrightness = 0.0;
if (iFace % 3 == 0) {
fBrightness = 1.0/7.0;
} else if (iFace % 3 == 2) {
fBrightness = 2.0/7.0;
} else {
fBrightness = 4.0/7.0;
}
var iBase = 16*iFace;
for (var iVertex = 0; iVertex < 4; ++iVertex) {
var iOffset = iBase + 4*iVertex;
gfaTransformedVertices[iOffset] = gfaVertices[iOffset];
gfaTransformedVertices[iOffset + 1] = gfaVertices[iOffset + 1];
gfaTransformedVertices[iOffset + 2] = gfaVertices[iOffset + 2];
gfaTransformedVertices[iOffset + 3] = gfaVertices[iOffset + 3];
gfaVertexColors[iOffset] = fBrightness;
gfaVertexColors[iOffset + 1] = fBrightness;
gfaVertexColors[iOffset + 2] = fBrightness;
gfaVertexColors[iOffset + 3] = .5;
}
}
for (var i = 0; i < 28+4*giAddedPoints; ++i) {
MultiplyMatrixVertex(faOrthoMatrix, gfaTransformedVertices, 4*i);
}
// We need to create the buffers afterward
CreateBuffers();
gqGL.clearColor(0.0, 0.0, 0.0, 1.0);
gqGL.enable(gqGL.DEPTH_TEST);
gqGL.clear(gqGL.COLOR_BUFFER_BIT | gqGL.DEPTH_BUFFER_BIT);
// Enable alpha blending
gqGL.enable(gqGL.BLEND);
// Set blending function
gqGL.blendFunc(gqGL.SRC_ALPHA, gqGL.ONE_MINUS_SRC_ALPHA);
gqGL.drawArrays(gqGL.POINTS, 24, 4);
// This does not work well
//const [kiMinSize, kiMaxSize] = gqGL.getParameter(gqGL.ALIASED_LINE_WIDTH_RANGE);
//gqGL.lineWidth(kiMaxSize);
//gqGL.drawArrays(gqGL.LINE_LOOP, 28, giAddedPoints);
// The six sides
gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 0, 4);
gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 4, 4);
gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 8, 4);
gqGL.drawArrays(gqGL.TRIANGLE_FAN, 28, giAddedPoints);
gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 12, 4);
gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 16, 4);
gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 20, 4);
}
// The plane is a 4d array of the form [A, B, C, D], where Ax + By + Cz + D = 0
// The box is a 6d array of the for [[xL, xH], [yL, yH], [zL, zH]], where xL and xH
// are the low and high values of the x plane defining the box, and so on.
function GetPlaneBoxIntersectionPolygon(faPlane, faaBox) {
// The points of intersection
var faPolyPoints = new Float32Array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
var iVertices = 0;
// Intersect the plane with each side line of the form [xL, yL, 0] + t[0, 0, 1] = [x, y, z]
// A(xL) + B(yL) + Ct + D = 0 => t = -(A*xL + B*yL + D)/C
// Check whether the z-value = t is in the range [zL, zH]. If it is zL or zH, we have a triple intersection.
// yz: 0-3; 0, zx: 4-7, xy: 8-11 : Individual edge indices are ordered ll, lh, hl, hh
// An array of booleans used to indicate whether the intersection with the edge was already found.
var baFound = [false, false, false, false, false, false, false, false, false, false, false, false];
// First the nonconstant dimension: the edges where x, y, and z vary, respectively
for (var i = 0; i < 3; ++i) {
if (faPlane[i] != 0.0) { // If the coefficeint is zero, it is parallel and does not intersect, but may coincide
for (var j = 0; j < 2; ++j) {
for (var k = 0; k < 2; ++k) {
var iEdge = 4*i + 2*j + k;
// This check is mostly used to avoid adding corner points three times.
if (!baFound[iEdge]) {
// Get the intersection
// Check its range with the ith dimension
// If it is in range log it
// If it is a corner, log and remove the other two edges
// Special case: The plane contains a line. In this case, we add both corners. (e.g. x + y = 0 and x = 1 and y = -1 are sides)
// Specail case: The plane contains a side. In this case, we add all four vertices and quit. ()
// When x is nonconstant, we have x = -(By1 + Cz1 + D)/A, call the solution dSol
var dSol = -faPlane[3]; // -D
// So, loop to get the other terms
for (var m = 1; m < 3; ++m) {
var iLowHigh = ((m == 1) ? j : k);
dSol -= faPlane[(i+m)%3]*faaBox[(i+m)%3][iLowHigh];
}
// Finally divide by the coefficient, since it is not zero.
dSol /= faPlane[i];
// Check whether the intersection is in the interval
if (dSol >= faaBox[i][0] && dSol <= faaBox[i][1]) {
// Take care of the special corner cases
if (dSol == faaBox[(i+m)%3][0]) {
// This dimension is low, So, it adds nothing to the index
baFound[((i+1)%3)*4 + 2*k ] = true;
baFound[((i+2)%3)*4 + j ] = true;
} else if (dSol == faaBox[(k+m)%3][1]) {
// This dimension is high, So, it adds to the index
baFound[((i+1)%3)*4 + 2*k + 1 ] = true;
baFound[((i+2)%3)*4 + 2 + j ] = true;
}
// Add the intersection and note it
baFound[iEdge] = true;
// Finally, add the point
faPolyPoints[4*iVertices + i] = dSol;
faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j];
faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k];
faPolyPoints[4*iVertices + 3] = 1.0;
++iVertices;
}
}
}
}
} else { // In this case, the plane may align with an edge or multiple edges
// Run though the four edges where this dimension is zero, and check whether the plane aligns with any of them
for (var j = 0; j < 2; ++j) {
for (var k = 0; k < 2; ++k) {
var iEdge = 4*i + 2*j + k;
if (!baFound[iEdge]) {
var bIsOnPlane = (faPlane[(i+1)%3]*faaBox[(i+1)%3][j] + faPlane[(i+2)%3]*faaBox[(i+2)%3][k] + faPlane[3] == 0.0);
if (bIsOnPlane) {
faPolyPoints[4*iVertices + i] = faaBox[i][0];
faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j];
faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k];
faPolyPoints[4*iVertices + 3] = 1.0;
++iVertices;
faPolyPoints[4*iVertices + i] = faaBox[i][1];
faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j];
faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k];
faPolyPoints[4*iVertices + 3] = 1.0;
++iVertices;
baFound[iEdge] = true;
// The low value. So, add nothing.
baFound[((i+1)%3)*4 + 2*k] = true;
baFound[((i+2)%3)*4 + j ] = true;
// The high value. So, 1 or 2.
baFound[((i+1)%3)*4 + 2*k + 1 ] = true;
baFound[((i+2)%3)*4 + 2 + j ] = true;
}
}
}
}
}
}
// Now we have all of the point of the polygon. We need to order them an put them into a returned polygon array.
// If we only have one or two points, we could return them as a special case of a point and a line segment. For, we will return null for all.
if (iVertices == 0) {
return null;
} else if (iVertices == 1){
return null;
} else if (iVertices == 2){
return null;
}
// Start with the first point and find each successive point with a common side.
// Find the one that is closest, in case the plane coincides with a side.
for (var i = 0; i < iVertices - 2; ++i) {
// Use the diagonal for the initial distance.
var fMinDistSq = (faaBox[0][0]-faaBox[0][1])*(faaBox[0][0]-faaBox[0][1]) +
(faaBox[1][0]-faaBox[1][1])*(faaBox[1][0]-faaBox[1][1]) +
(faaBox[2][0]-faaBox[2][1])*(faaBox[2][0]-faaBox[2][1]);
var iClosetOnSameSide = 0;
for (var j = i+1; j < iVertices; ++j) {
var bOnSameSide = false;
for (var k = 0; k < 3; ++k) {
if ((faPolyPoints[4*i + k] == faPolyPoints[4*j + k]) &&
(faPolyPoints[4*i + k] == faaBox[k][0] || faPolyPoints[4*i + k] == faaBox[k][1])) {
bOnSameSide = true;
}
}
if (bOnSameSide) {
// Check whether the distance is smallest
var fDistSq = 0.0
for (var k = 0; k < 3; ++k) {
var dDiff = (faPolyPoints[4*i + k]-faPolyPoints[4*j + k]);
fDistSq += dDiff*dDiff;
}
if (fDistSq < fMinDistSq) {
iClosetOnSameSide = j;
fMinDistSq = fDistSq;
}
}
}
// Now we have the index of the closet vertex that is on the same side, make it next
for (var j = 0; j < 3; ++j) {
// Swap the currrent coordinate for the first point
var dSwap = faPolyPoints[4*(i + 1) + j];
faPolyPoints[4*(i + 1) + j] = faPolyPoints[4*iClosetOnSameSide + j];
faPolyPoints[4*iClosetOnSameSide + j] = dSwap;
}
}
// Copy the points back into an array
// I could add in a procedure to reverse the points if they are in the wrong order. They should probably be made to be always visible.
var faPoly = new Float32Array(4*iVertices);
for (var i = 0; i < 4*iVertices; ++i) {
faPoly[i] = faPolyPoints[i];
}
return faPoly;
}
// Multiply the four coordinate vertex in V at the start index
function MultiplyMatrixVertex(faM, faV, iStart) { // V = M*V
var faCopy = [0,0,0,0];
for (var i = 0; i < 4; ++i) {
faCopy[i] = faV[iStart + i];
}
for (iRow = 0; iRow < 4; ++iRow) {
faV[iStart + iRow] = faM[iRow]*faCopy[0] + faM[iRow + 4]*faCopy[1] + faM[iRow + 8]*faCopy[2] + faM[iRow + 12]*faCopy[3];
}
}
function Normalize(faV) {
var fL = Math.sqrt(faV[0]*faV[0] + faV[1]*faV[1] + faV[2]*faV[2]);
faV[0] /= fL; faV[1] /= fL; faV[2] /= fL;
}
function Dot(faV1, faV2) {
return (faV1[0]*faV2[0] + faV1[1]*faV2[1] + faV1[2]*faV2[2]);
}
function Cross(faV1, faV2) {
return [faV1[1]*faV2[2]-faV1[2]*faV2[1], faV1[2]*faV2[0]-faV1[0]*faV2[2], faV1[0]*faV2[1]-faV1[1]*faV2[0]];
}
function Difference(faV1, faV2) {
return [faV1[0]-faV2[0], faV1[1]-faV2[1], faV1[2]-faV2[2]];
}
function CreateLookAtMatrix(faEye, faObject, faUp) {
var faViewDirection = Difference(faObject, faEye);
Normalize(faViewDirection);
var faRight = Cross(faViewDirection, faUp);
Normalize(faRight);
var faStraightUp = Cross(faRight, faViewDirection);
var faMatrix = new Float32Array([
faRight[0], faStraightUp[0], faViewDirection[0], 0.0,
faRight[1], faStraightUp[1], faViewDirection[1], 0.0,
faRight[2], faStraightUp[2], faViewDirection[2], 0.0,
-Dot(faObject, faRight), -Dot(faObject, faStraightUp), -Dot(faObject, faViewDirection), 1.0]);
return faMatrix;
}
function CreateOrthographicMatrix(fLeft, fRight, fBottom, fTop, fNear, fFar) {
if (fLeft >= fRight || fBottom >= fTop || fFar >= fNear) {
throw 'Improper Orthographic Projection Matrix';
}
fDx = fRight - fLeft;
fDy = fTop - fBottom;
fDz = fNear - fFar;
var faMatrix = new Float32Array([
2.0/fDx, 0.0, 0.0, 0.0,
0.0, 2.0/fDy, 0.0, 0.0,
0.0, 0.0, 2.0/fDz, 0.0,
-(fLeft + fRight)/fDx, -(fBottom + fTop)/fDy, -(fNear + fFar)/fDz, 1.0]);
return faMatrix;
}
function MultiplyMatrices(faaM, faaA) { // M = M*A, Note M != A
var faRow = [0,0,0,0];
for (iRow = 0; iRow < 4; ++iRow) {
// Copy the current row
for(iCol = 0; iCol < 4; ++iCol) {
faRow[iCol] = faaM[iRow + 4*iCol];
}
for(iCol = 0; iCol < 4; ++iCol) {
faaM[iRow + 4*iCol] = 0.0;
for (k = 0; k < 4; ++k) {
faaM[iRow + 4*iCol] += faRow[k]*faaA[4*iCol + k];
}
}
}
}
</script>
</head>
<body onload="Initialization()">
<canvas id="idCanvas" width="800", height="800" style="border:1px solid blue"></canvas>
</body>
</html>© 20072025 XoaX.net LLC. All rights reserved.